package Gr

import (
	"fmt"
	"db2s/ETL"
	"db2s/global"
	"db2s/outPut"
	"runtime"
	"sort"
	"strings"
	"time"
)

type TableActive struct {
}

func (cs TableActive) Truncate(s global.TableActiveInput) (err error) {
	var (
		event       string
		execTimeout int64
		tableName   string
		truncateSql []string
	)
	tableName = fmt.Sprintf("`%v`.`%v`", s.TableInfo.Schema, s.TableInfo.Table)
	if len(s.Input.Partition.PartitionGather) > 0 {
		for _, v := range s.Input.Partition.PartitionGather {
			if strings.EqualFold(v, "single") {
				sql := fmt.Sprintf("truncate table %v", tableName)
				truncateSql = append(truncateSql, sql)
			} else {
				if len(v) > 0 {
					sql := fmt.Sprintf("ALTER TABLE %v TRUNCATE PARTITION %v", tableName, v)
					truncateSql = append(truncateSql, sql)
				}
			}
		}
	} else {
		sql := fmt.Sprintf("truncate table %v", tableName)
		truncateSql = append(truncateSql, sql)
	}
	if _, err = ExecUpdateSql(ExecSqlInputS{
		Ss:           s,
		Event:        event,
		UpdateSqlStr: truncateSql,
		ShardName:    "single",
	}); err != nil {
		return err
	}
	vlog = fmt.Sprintf("(%d) %v A sql statement was executed successfully. table is %v.%v, exec timeout is {%vms}", s.LogSeq, event, s.TableInfo.Schema, s.TableInfo.Table, execTimeout)
	WLog.Debug(vlog)
	return
}

// Analyze analyze table 收集统计信息
func (cs TableActive) Analyze(s global.TableActiveInput) (any, error) {
	var (
		setVariables = []string{"SET session unique_checks=0", "SET session foreign_key_checks=0"}
		event        = "[ApplyAnalyze]"
		execTimeout  int64
		shardName    string
		err          error
	)
	pc, _, _, _ := runtime.Caller(1)
	callingFunc := runtime.FuncForPC(pc)
	callFile, callLine := callingFunc.FileLine(pc)
	callFunc := callingFunc.Name()
	callFuncInfo := fmt.Sprintf("Call functions %v Call File %v:%v", callFunc, callFile, callLine)
	vlog = fmt.Sprintf("(%d) %v %v Table %v.%v starts executing the analyze table statement", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table)
	WLog.Debug(vlog)
	shardName = "single"
	if !s.Optimizer.SqlMode {
		setVariables = append(setVariables, "set session sql_mode=''")
	}
	active := ETL.InstanceActiveS{SessionSet: setVariables, DB: s.DB[shardName], StopTime: s.SqlExecStopTime, SqlLogBin: s.Optimizer.SqlLogBin}
	/*
		mysql 单机bug，lower_case_table_names = 1 , use schema xxx ,执行analyze table 表名为大写时，不收集统计信息，只能改为小写
	*/
	analyzeTable := fmt.Sprintf("analyze table `%v`", strings.ToLower(s.TableInfo.Table))
	use := fmt.Sprintf("use `%v`", s.TableInfo.Schema)
	sqlstr := []*string{&use, &analyzeTable}
	if execTimeout, err = active.TrxUpdate(sqlstr); err != nil {
		vlog = fmt.Sprintf("(%d) %v %v The execution of a sql statement failed. sql is {%v},error is {%v}", s.LogSeq, callFuncInfo, event, sqlstr, err)
		WLog.Error(vlog)
		return nil, err
	}
	vlog = fmt.Sprintf("(%d) %v %v A sql statement was executed successfully. sql is {%v}, exec timeout is {%vms}", s.LogSeq, callFuncInfo, event, sqlstr, execTimeout)
	WLog.Debug(vlog)
	return execTimeout, nil
}

type keyValuePair struct {
	key   string
	value int64
}

// OptimizerOrderBy 同一组内多表进行数据操作时，针对表做排序，按照最优排序
func (cs TableActive) OptimizerOrderBy(s global.TableActiveInput) (any, error) {
	// 将 map 转换为 slice 类型
	var (
		pairs           []keyValuePair
		tableOrderBySeq []string
		pollingGroup    []string
		pollingMap      = make(map[string]int)
	)
	// --按照rows arv length进行排序
	for k, v := range s.OptimizerOrderBy.SrcArgRowsLength {
		pairs = append(pairs, keyValuePair{k, v.(int64)})
	}
	// 对 slice 进行排序,根据 int64 值进行排序
	sort.Slice(pairs, func(i, j int) bool {
		return pairs[i].value < pairs[j].value
	})
	// -- 按照目标端shard轮询的方式进行拆分
	for _, v := range s.OptimizerOrderBy.DestShardName {
		if !strings.HasPrefix(strings.ToLower(v), "shard") {
			pollingMap["partition"]++
		} else {
			pollingMap[v]++
		}
	}
	for k := range pollingMap {
		pollingGroup = append(pollingGroup, k)
	}
	sort.Strings(pollingGroup)
	//shardData 分组
	var shardData = make(map[string][]string)
	for _, name := range pollingGroup {
		shardData[name] = make([]string, 0, (len(pairs)+len(pollingGroup)-1)/len(pollingGroup))
	}
	//按照shard合并表
	for _, v := range pairs {
		for k1, shard := range s.OptimizerOrderBy.DestShardName {
			if strings.EqualFold(v.key, k1) {
				if !strings.HasPrefix(strings.ToLower(shard), "shard") {
					shard = "partition"
				}
				shardData[shard] = append(shardData[shard], v.key)
			}
		}
	}
	//按照shard名进行逐一选取
	var tableSeq int
	for _, v := range shardData {
		if len(v) > tableSeq {
			tableSeq = len(v)
		}
	}
	for i := 0; i < tableSeq; i++ {
		for _, shardName := range pollingGroup {
			if shardD, ok := shardData[shardName]; ok {
				if i < len(shardD) {
					tableOrderBySeq = append(tableOrderBySeq, shardD[i])
				}
			}
		}
	}
	return tableOrderBySeq, nil
}

func (cs TableActive) WriteAlter(s global.TableActiveInput) (any, error) {
	var beginTime = time.Now()
	if err := s.Alter.FilePoint1.(outPut.FileOperations).Write("", s.Alter.SqlGather); err != nil {
		return nil, err
	}
	timeOut := time.Since(beginTime).Milliseconds()
	return timeOut, nil
}
func (cs TableActive) ExecAlter(s global.TableActiveInput) (res any, err error) {
	var (
		event       = "[ApplyAlter]"
		execTimeout int64
	)
	//var (
	//	event        = "[ApplyAlter]"
	//	setVariables = []string{"SET session unique_checks=0", "SET session foreign_key_checks=0"}
	//	execTimeout  int64
	//	shardName    string
	//	err          error
	//	sqlText      *string
	//)
	//pc, _, _, _ := runtime.Caller(1)
	//callingFunc := runtime.FuncForPC(pc)
	//callFile, callLine := callingFunc.FileLine(pc)
	//callFunc := callingFunc.Name()
	//callFuncInfo := fmt.Sprintf("Call functions %v Call File %v:%v", callFunc, callFile, callLine)
	//vlog = fmt.Sprintf("(%d) %v %v Table %v.%v starts executing the alter repair statement", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table)
	//WLog.Debug(vlog)
	//shardName = "single"
	//if !s.Input.SqlMode {
	//	setVariables = append(setVariables, "set session sql_mode=''")
	//}
	//setVariables = append(setVariables, "set innodb_strict_mode=off")
	//active := dataDispos.InstanceActiveS{SessionSet: setVariables, DB: s.DB[shardName], SqlLogBin: s.Input.SqlLogBin, SqlMode: s.Input.SqlMode}
	//if _, sqlText, err = active.TrxAlterUpdate(s.Alter.SqlGather); err != nil {
	//	vlog = fmt.Sprintf("(%d) %v %v The execution of a sql statement failed. table is %v.%v,sql text is {%v} error is {%v}", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table, *sqlText, err)
	//	WLog.Error(vlog)
	//	return sqlText, err
	//}
	//vlog = fmt.Sprintf("(%d) %v %v A sql statement was executed successfully. table is %v.%v, exec timeout is {%vms}", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table, execTimeout)
	//WLog.Debug(vlog)
	if execTimeout, err = ExecUpdateSql(ExecSqlInputS{
		Ss:           s,
		Event:        event,
		UpdateSqlStr: s.Alter.SqlGather,
		ShardName:    "single",
	}); err != nil {
		return
	}
	return execTimeout, nil
}

// Alter 操作表的alter语句
func (cs TableActive) Alter(s global.TableActiveInput) (any, error) {
	if strings.EqualFold(s.Alter.Active, "file") {
		return cs.WriteAlter(s)
	} else if strings.EqualFold(s.Alter.Active, "table") {
		return cs.ExecAlter(s)
	}
	return nil, nil
}

// CreateDatabase 收集统计信息
func (cs TableActive) CreateDatabase(s global.TableActiveInput) (any, error) {
	var (
		setVariables = []string{"SET session unique_checks=0", "SET session foreign_key_checks=0"}
		event        = "[CreateDatabase]"
		execTimeout  int64
		shardName    string
		err          error
	)
	pc, _, _, _ := runtime.Caller(1)
	callingFunc := runtime.FuncForPC(pc)
	callFile, callLine := callingFunc.FileLine(pc)
	callFunc := callingFunc.Name()
	callFuncInfo := fmt.Sprintf("Call functions %v Call File %v:%v", callFunc, callFile, callLine)
	vlog = fmt.Sprintf("(%d) %v %v Table %v.%v starts executing the analyze table statement", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table)
	WLog.Debug(vlog)
	shardName = "single"
	if !s.Optimizer.SqlMode {
		setVariables = append(setVariables, "set session sql_mode=''")
	}
	active := ETL.InstanceActiveS{SessionSet: setVariables, DB: s.DB[shardName], SqlLogBin: s.Input.SqlLogBin}
	/*
		mysql 单机bug，lower_case_table_names = 1 , use schema xxx ,执行analyze table 表名为大写时，不收集统计信息，只能改为小写
	*/
	createDatabase := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%v`", s.TableInfo.Schema)
	sqlstr := []*string{&createDatabase}
	if execTimeout, err = active.TrxUpdate(sqlstr); err != nil {
		vlog = fmt.Sprintf("(%d) %v %v The execution of a sql statement failed. sql is {%v},error is {%v}", s.LogSeq, callFuncInfo, event, sqlstr, err)
		WLog.Error(vlog)
		return nil, err
	}
	return execTimeout, nil
}

// Create 收集统计信息
func (cs TableActive) Create(s global.TableActiveInput) (any, error) {
	var (
		setVariables = []string{"SET session unique_checks=0", "SET session foreign_key_checks=0"}
		execTimeout  int64
		shardName    string
		err          error
	)
	shardName = "single"
	if !s.Optimizer.SqlMode {
		setVariables = append(setVariables, "set session sql_mode=''")
	}
	active := ETL.InstanceActiveS{SessionSet: setVariables, DB: s.DB[shardName], SqlLogBin: s.Input.SqlLogBin}
	sqlStr := []*string{&s.Create.ExecSql}
	if execTimeout, err = active.DDL(sqlStr); err != nil {
		return nil, err
	}
	return execTimeout, nil
}

// Drop 收集统计信息
func (cs TableActive) Drop(s global.TableActiveInput) (res any, err error) {
	var event = "[drop]"
	var execTimeout int64
	if execTimeout, err = ExecUpdateSql(ExecSqlInputS{
		Ss:           s,
		Event:        event,
		UpdateSqlStr: s.Drop.ExecSql,
		ShardName:    "single",
	}); err != nil {
		return
	}
	return execTimeout, nil
}

// Recover 收集统计信息
func (cs TableActive) Recover(s global.TableActiveInput) (any, error) {
	var (
		setVariables = []string{"SET session unique_checks=0", "SET session foreign_key_checks=0"}
		event        = "[Recover]"
		execTimeout  int64
		shardName    string
		err          error
	)
	pc, _, _, _ := runtime.Caller(1)
	callingFunc := runtime.FuncForPC(pc)
	callFile, callLine := callingFunc.FileLine(pc)
	callFunc := callingFunc.Name()
	callFuncInfo := fmt.Sprintf("Call functions %v Call File %v:%v", callFunc, callFile, callLine)
	vlog = fmt.Sprintf("(%d) %v %v Table %v.%v starts executing the analyze table statement", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table)
	WLog.Debug(vlog)
	shardName = "single"
	if !s.Optimizer.SqlMode {
		setVariables = append(setVariables, "set session sql_mode=''")
	}
	active := ETL.InstanceActiveS{SessionSet: setVariables, DB: s.DB[shardName], SqlLogBin: s.Input.SqlLogBin}
	renameTable := fmt.Sprintf("rename table `%v`.`%v` to `%v`.`%v` ", s.TableInfo.Schema, s.TableInfo.Table, s.TableInfo.RecoverDbName, s.TableInfo.RecoverTableName)
	createTable := fmt.Sprintf("create table `%v`.`%v` like `%v`.`%v` ", s.TableInfo.Schema, s.TableInfo.Table, s.TableInfo.RecoverDbName, s.TableInfo.RecoverTableName)
	sqlStr := []*string{&renameTable, &createTable}
	if execTimeout, err = active.TrxUpdate(sqlStr); err != nil {
		vlog = fmt.Sprintf("(%d) %v %v The execution of a sql statement failed. sql is {%v},error is {%v}", s.LogSeq, callFuncInfo, event, sqlStr, err)
		WLog.Error(vlog)
		return false, err
	}
	vlog = fmt.Sprintf("(%d) %v %v A sql statement was executed successfully. sql is {%v}, exec timeout is {%vms}", s.LogSeq, callFuncInfo, event, sqlStr, execTimeout)
	WLog.Debug(vlog)
	return execTimeout, nil
}
